<template>
	<div>
		<h3>
			<span class="icon is-grey">
				<Icon icon="align-left" />
			</span>
			{{ $t('task.attributes.description') }}
			<CustomTransition name="fade">
				<span
					v-if="loading && saving"
					class="is-small is-inline-flex"
				>
					<span class="loader is-inline-block mr-2" />
					{{ $t('misc.saving') }}
				</span>
				<span
					v-else-if="!loading && saved"
					class="is-small has-text-success"
				>
					<Icon icon="check" />
					{{ $t('misc.saved') }}
				</span>
			</CustomTransition>
		</h3>
		<Editor
			v-model="description"
			class="tiptap__task-description"
			:is-edit-enabled="canWrite"
			:upload-callback="uploadCallback"
			:placeholder="$t('task.description.placeholder')"
			:show-save="true"
			edit-shortcut="e"
			:enable-discard-shortcut="true"
			@update:modelValue="saveWithDelay"
			@save="save"
		/>
	</div>
</template>

<script setup lang="ts">
import {ref, computed, watch, onBeforeUnmount} from 'vue'

import CustomTransition from '@/components/misc/CustomTransition.vue'
import Editor from '@/components/input/AsyncEditor'

import type {ITask} from '@/modelTypes/ITask'
import {useTaskStore} from '@/stores/tasks'

type AttachmentUploadFunction = (file: File, onSuccess: (attachmentUrl: string) => void) => Promise<string>

const {
	modelValue,
	attachmentUpload,
	canWrite,
} = defineProps<{
	modelValue: ITask,
	attachmentUpload: AttachmentUploadFunction,
	canWrite: boolean,
}>()

const emit = defineEmits(['update:modelValue'])

const description = ref<string>('')
const saved = ref(false)

// Since loading is global state, this variable ensures we're only showing the saving icon when saving the description.
const saving = ref(false)

const taskStore = useTaskStore()
const loading = computed(() => taskStore.isLoading)

watch(
	() => modelValue.description,
	value => {
		description.value = value
	},
	{immediate: true},
)

const changeTimeout = ref<ReturnType<typeof setTimeout> | null>(null)

async function saveWithDelay() {
	if (changeTimeout.value !== null) {
		clearTimeout(changeTimeout.value)
	}

	changeTimeout.value = setTimeout(async () => {
		await save()
	}, 5000)
}

onBeforeUnmount(() => {
	if (changeTimeout.value !== null) {
		clearTimeout(changeTimeout.value)
	}
})

async function save() {
	if (changeTimeout.value !== null) {
		clearTimeout(changeTimeout.value)
	}

	saving.value = true

	try {
		// FIXME: don't update state from internal.
		const updated = await taskStore.update({
			...modelValue,
			description: description.value,
		})
		emit('update:modelValue', updated)

		saved.value = true
		setTimeout(() => {
			saved.value = false
		}, 2000)
	} finally {
		saving.value = false
	}
}

async function uploadCallback(files: File[] | FileList): (Promise<string[]>) {

	const uploadPromises: Promise<string>[] = []

	files.forEach((file: File) => {
		const promise = new Promise<string>((resolve) => {
			attachmentUpload(file, (uploadedFileUrl: string) => resolve(uploadedFileUrl))
		})

		uploadPromises.push(promise)
	})

	return await Promise.all(uploadPromises)
}
</script>

<style lang="scss" scoped>
.tiptap__task-description {
	// The exact amount of pixels we need to make the description icon align with the buttons and the form inside the editor.
	// The icon is not exactly the same length on all sides so we need to hack our way around it.
	margin-left: 4px;
}
</style>
